# 前言

# js中的数据类型

JavaScript 中共有七种内置数据类型,包括基本类型和对象类型。

# 基本类型

基本类型(六种):

  • string(字符串)
  • boolean(布尔值)
  • number(数字)
  • symbol(符号)
  • null(空值)
  • undefined(未定义)

stringnumberbooleannull undefined 这五种类型统称为原始类型(Primitive),表示不能再细分下去的基本类型。

symbol 是ES6中新增的数据类型,symbol 表示独一无二的值,通过 Symbol() 函数调用生成,由于生成的 symbol 值为原始类型,所以 Symbol() 函数不能使用 new 调用;

nullundefined 通常被认为是特殊值,这两种类型的值是唯一的,就是其本身。

# 对象类型

对象类型也叫引用类型,arrayfunction 是对象的子类型。对象在逻辑上是属性的无序集合,是存放各种值的容器。对象值存储的是引用地址,所以和基本类型值不可变的特性不同,对象值是可变的。

# js是弱类型语言

JavaScript 是弱类型语言,而且JavaScript 声明变量的时候并没有预先确定的类型,变量的类型就是其值的类型,也就是说变量当前的类型由其值所决定,夸张点说上一秒种的String,下一秒可能就是个Number类型了,这个过程可能就进行了某些操作发生了强制类型转换。

虽然弱类型的这种不需要预先确定类型的特性给我们带来了便利,同时也会给我们带来困扰。为了能充分利用该特性就必须掌握类型转换的原理,

# 类型判断

有以下方式:

  • typeof
  • instanceof
  • Object.prototype.toString()

# typeof

只能判定除 null 以外的基本类型,对象只能区分 function

console.log(
  typeof 'hello', // 'string'
  typeof true, // 'boolean'
  typeof 10, // 'number'
  typeof Symbol(), // 'symbol'
  typeof undefined, // 'undefined'
  typeof null, // 'object' 无法判定是否为 null

  typeof {}, // 'object'
  typeof [], // 'object' 无法区分对象结构是否为数组
  typeof(() => {}), // 'function'
)
1
2
3
4
5
6
7
8
9
10
11
12

# instanceof

通过 instanceof 操作符也可以对对象类型进行判定,其原理就是测试构造函数的 prototype 是否出现在被检测对象的原型链上。

[] instanceof Array            // true
({}) instanceof Object         // true
(()=>{}) instanceof Function   // true
1
2
3

instanceof 也不是万能的。

const obj = {}
obj.__proto__ = Array.prototype

console.log(
  [] instanceof Array, // true
  [] instanceof Object, // true

  ({}) instanceof Object, // true

  (()=>{}) instanceof Function, // true
  (()=>{}) instanceof Object, // true

  obj instanceof Object, // true
  obj instanceof Array, // true
  obj, // Array {}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因为 Array 属于 Object 子类型,即:

Array.prototype.__proto__ === Object.prototype
1

Object 的构造函数在 数组实例 的原型链上。所以 instanceof 仍然无法准确地判断一个值到底属于数组还是普通对象。

# Object.prototype.toString()

这是判断数据类型最精确的方法。

console.log(
  Object.prototype.toString.call({}),              // '[object Object]'
  Object.prototype.toString.call([]),              // '[object Array]'
  Object.prototype.toString.call(() => {}),        // '[object Function]'
  Object.prototype.toString.call('hello'),         // '[object String]'
  Object.prototype.toString.call(1),               // '[object Number]'
  Object.prototype.toString.call(true),            // '[object Boolean]'
  Object.prototype.toString.call(Symbol()),        // '[object Symbol]'
  Object.prototype.toString.call(null),            // '[object Null]'
  Object.prototype.toString.call(undefined),       // '[object Undefined]'
  
  Object.prototype.toString.call(new Date()),      // '[object Date]'
  Object.prototype.toString.call(Math),            // '[object Math]'
  Object.prototype.toString.call(new Set()),       // '[object Set]'
  Object.prototype.toString.call(new WeakSet()),   // '[object WeakSet]'
  Object.prototype.toString.call(new Map()),       // '[object Map]'
  Object.prototype.toString.call(new WeakMap()),   // '[object WeakMap]'
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

该方法本质就是依托 Object.prototype.toString() 方法得到对象内部属性 [[Class]] 传入原始类型却能够判定出结果是因为对值进行了包装,比如 new Number(1) nullundefined 能够输出结果是内部实现有做处理

# 类型转换

# 对象转换为原始值 ToPrimitive ( toString()/valueOf() )

ToPrimitive对原始类型不发生转换处理,只针对引用类型(object)的,其目的是将引用类型(object)转换为非对象类型,也就是原始类型。

ToPrimitive 运算符接受一个值 obj ,和一个可选的 期望类型 type 作参数。ToPrimitive 运算符将值转换为非对象类型,如果对象有能力被转换为不止一种原语类型,可以使用可选的 期望类型 来暗示那个类型。

/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/

ToPrimitive(obj,type)
1
2
3
4
5
6

type不同值的说明:

  • type'string':
  1. 先调用 objtoString 方法,如果为原始值,则return,否则第2步

  2. 调用 objvalueOf 方法,如果为原始值,则 return ,否则第3步

  3. 抛出 TypeError 异常

  • type'number':
  1. 调用 objvalueOf 方法,如果为原始值,则返回,否则下第2步

  2. 调用 objtoString 方法,如果为原始值,则 return ,否则第3步

  3. 抛出 TypeError 异常

  • type 参数为空
  1. 该对象为 Date ,则 type 被自动设置为 string

  2. 否则, type 被自动设置为 Number

Date数据类型特殊说明:

对于Date数据类型,我们更多期望获得的是其转为时间后的字符串,而非毫秒值(时间戳),如果为 number ,则会取到对应的毫秒值,显然字符串使用更多。其他类型对象按照取值的类型操作即可。

ToPrimitive总结:

ToPrimitive转成何种原始类型,取决于typetype参数可选,若指定,则按照指定类型转换,若不指定,默认根据实用情况分两种情况,Date对象默认type值为'string',其余对象为'number'

那么什么时候会指定 type 类型呢,那就要看下面两种转换方式了。

# toString()

Object.prototype.toString()
1

toString() 方法返回一个表示该对象的字符串。

每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。

这里先记住,valueOf()toString() 在特定的场合下会自行调用。

# valueOf()

Object.prototype.valueOf()
1

valueOf() 方法返回指定对象的原始值。

JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是很少需要手动调用此函数,通常都会被 JavaScript 自动调用。

# 不同内置对象默认的 valueOf 实现

  • String => 返回字符串值
  • Number => 返回数字值
  • Date => 返回一个数字,即时间值,字符串中内容是依赖于具体实现的
  • Boolean => 返回Boolean的this值
  • Object => 返回this
var str = new String('123');
console.log(str.valueOf());//123

var num = new Number(123);
console.log(num.valueOf());//123

var date = new Date();
console.log(date.valueOf()); //1526990889729

var bool = new Boolean('123');
console.log(bool.valueOf());//true

var obj = new Object({valueOf:()=>{
    return 1
}})
console.log(obj.valueOf());//1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 强制转换

# Number(param) 转换规则

param 为下列不同值时的转换规则:

  • null 转换为 0
  • undefined 转换为 NaN
  • true 转换为 1false 转换为 0
  • string 类型转换时遵循数字常量规则,转换失败返回 NaN
  • object 类型要先转换为原始值,先调用 ToPrimitive 转换,type指定为number了

# String(param) 转换规则

param 为下列不同值时的转换规则:

  • null 转换为 'null'
  • undefined 转换为 'undefined'
  • true 转换为 'true'false 转换为 'false'
  • number 数字转换遵循通用规则,极大极小的数字使用指数形式
  • object 对象这里要先转换为原始值,先调用 ToPrimitive 转换,type就指定为string了
String(null)                 // 'null'
String(undefined)            // 'undefined'
String(true)                 // 'true'
String(1)                    // '1'
String(-1)                   // '-1'
String(0)                    // '0'
String(-0)                   // '0'
String(Math.pow(1000,10))    // '1e+30'
String(Infinity)             // 'Infinity'
String(-Infinity)            // '-Infinity'
String({})                   // '[object Object]'
String([1,[2,3]])            // '1,2,3'
String(['koala',1])          //koala,1
1
2
3
4
5
6
7
8
9
10
11
12
13

# Boolean(param) 转换规则

param 为以下值时转换结果为 false,否则为 true

  • undefined
  • null
  • 0/+0/-0
  • NaN
  • ''(空字符串)

注意:所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
1
2
3
4
5
6
7
8
9

# 隐式转换

# 会自动转换为 string 类型的情况

  • 字符串 + 原始类型:后者自动转为字符串

    console.log(
      '2' + 1, // '21'
      '2' + true, // "2true"
      '2' + false, // "2false"
      '2' + undefined, // "2undefined"
      '2' + null, // "2null"
    )
    
    1
    2
    3
    4
    5
    6
    7
  • 字符串 + 对象:

    // 字符串 + 几种对象
    console.log(
      '2' + { a: 1 }, // '2[object Object]'
      '2' + [], // '2'
      '2' + function () {}, // '2function () {}'
      '2' + ['koala',1], // '2koala,1'
    )
    
    // 字符串 + 重写了 toString() / valueOf() 方法的对象
    const obj = {
      toString () {
        return 'a'
      },
      valueOf () {
        return 9
      }
    }
    // 在 + 号运算符中,对象的 ToPrimitive 转换 type 都默认为 'number',先调用 valueOf。
    console.log(
      1 + obj, // 10
      '1' + obj, // '19'
      true + obj // 10
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

注意:不管是不是对象,都有一个转换为原始值的过程,也就是ToPrimitive转换,只不过原始类型转换后不发生变化,对象类型才会发生具体转换。

# 会自动转换为 number 类型的情况

  • 在加法运算符中,没有 string 类型的时候,自动转换为 number 类型

    true + 0 // 1
    true + true // 2
    true + false //1
    
    1
    2
    3
  • 除了加法运算符,其他运算符都自动转换为 number 类型

    console.log(
      '5' - '2', // 3
      '5' * '2', // 10
      true - 1, // 0
      false - 1, // -1
      '1' - 1, // 0
      '5' * [], // 0
      false / '5', // 0
      'abc' - 1, // NaN
      null + 1, // 1
      undefined + 1, // NaN
    )
    
    //一元运算符也会转为 number 类型
    console.log(
      +'abc', // NaN
      -'abc', // NaN
      +true, // 1
      -false, // 0
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • == 抽象相等比较时,也是自动转为 number 类型(== 两边都执行 Number()

    // 如果存在对象,ToPrimitive() 的 type 参数默认值为 number 进行转换,再进行比较
    const obj1 = {
      valueOf () {
          return '1'
      }
    }
    
    console.log(
      1 == obj1 //true //相当于 Number(1) == Number(obj)
    )
    
    console.log(
      [] == [], // false // 不发生类型转换
      [] == ![], // true // ![] 为 false, 相当于 Number([]) == Number(false)
    )
    // [] 作为对象,ToPrimitive 转换调用 valueOf() 得到  [], 再调用 toString() 得到 ''
    // Number('') == Number(false) // 0 == 0 // true
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# 会自动转换为 boolean 类型的情况

变量 a 在以下情况会被自动转换为 boolean 值,执行 Boolean() 操作。

  • !a
  • a && b
  • if (a) {}
  • while (a) {}
  • a ? 1 : 0
  • ...

# NaN

NaN 是一个全局对象的属性,NaN 是一种特殊的 number 类型。

返回 NaN 的情况:

  • 无穷大除以无穷大
  • 给任意负数做开方运算
  • 算数运算符两侧包含 非数值 或 无法转换为数值的值
  • 字符串无法被解析成数值
// 以下全部返回 NaN

console.log(
  Infinity / Infinity, // 无穷大除以无穷大
  Math.sqrt(-1), // 给任意负数做开方运算
  'a' - 1, // 算数运算符两侧包含 非数值 或 无法转换为数值的值
  'a' * 1, // 同上
  'a' / 1, // 同上
  parseInt('a'), // 字符串无法被解析成数值
  parseFloat('a'), // 同上
  Number('a'), // 同上
  +'abc', // 同上
  -'abc', // 同上
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# toString()String() 的区别

# toString()

  • toString()可以将数据都转为字符串,但是nullundefined不可以转换。

    //报错 TypeError: Cannot read property 'toString' of null
    console.log(null.toString())
    
    //报错 TypeError: Cannot read property 'toString' of undefined
    console.log(undefined.toString())
    
    1
    2
    3
    4
    5
  • toString() 括号中可以写数字,代表进制

    let num = 10
    console.log(
      num.toString(2), // 1010 二进制
      num.toString(8), // 12 八进制
      num.toString(), // 10 十进制
      num.toString(16), // a 十六进制
    )
    
    1
    2
    3
    4
    5
    6
    7

# String()

String() 可以将 nullundefined 转换为字符串,但是没法转进制字符串

console.log(
  String(null), // null
  String(undefined), // undefined
)
1
2
3
4